package compiler;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import language.LArray;
import language.LCharacter;
import language.LNumber;
import language.LType;

public class Compiler {

    private final Map<String, LType> constants = language.Constants.constants();

    public Executable compile(String s) {
        s = removeComments(s);
        List<Character> chars = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '.' && s.charAt(i + 1) == 'u') {
                chars.add((char) Integer.parseInt(s.substring(i + 2, i + 6), 16));
                i += 5;
            } else {
                chars.add(s.charAt(i));
            }
        }
        char[] arr = new char[chars.size()];
        for (int i = 0; i < chars.size(); i++) {
            arr[i] = chars.get(i);
        }
        List<List> lists = new ArrayList();
        {
            List list = new ArrayList();
            for (int i = 0; i < arr.length; i++) {
                char c = arr[i];
                if (c == '"') {
                    List<LCharacter> str = new ArrayList();
                    i++;
                    for (; i < arr.length && (c = arr[i]) != '"'; i++) {
                        if (c == '.' && arr.length > i + 1) {
                            if (arr[i + 1] == '.' || arr[i + 1] == '"') {
                                str.add(new LCharacter(arr[++i]));
                            } else if (arr[i + 1] == 'U') {
                                str.add(new LCharacter('.'));
                                str.add(new LCharacter('u'));
                                i++;
                            } else if (arr[i + 1] == '=') {
                                str.add(new LCharacter('.'));
                                str.add(new LCharacter('-'));
                                i++;
                            } else {
                                str.add(new LCharacter(c));
                            }
                        } else {
                            str.add(new LCharacter(c));
                        }
                    }
                    list.add(LArray.of(str));
                } else if (c == '\'') {
                    list.add(new LCharacter(arr[++i]));
                } else {
                    list.add(c);
                }
            }
            lists.add(list);
            for (int i = 0; i < list.size() - 1; i++) {
                if (list.get(i).equals('[')) {
                    if (list.get(i + 1) instanceof Character) {
                        char c = (Character) list.get(i + 1);
                        if (c >= 'A' && c <= 'Z') {
                            List newList = new ArrayList();
                            for (int j = i; j < list.size(); j++) {
                                newList.add(list.get(j));
                            }
                            for (int j = i; j < list.size();) {
                                list.remove(j);
                            }
                            list = newList;
                            lists.add(list);
                            i = 0;
                        }
                    }
                }
            }
        }
        for (List list : lists) {
            expandBraces(list);
            closeBrackets(list);
            for (int j = 0; j < list.size(); j++) {
                boolean flag = false;
                if (list.get(j) instanceof Character) {
                    char c = (Character) list.get(j);
                    if (c >= '0' && c <= '9') {
                        flag = true;
                    }
                    if (c == '-') {
                        if (list.size() > j + 1 && list.get(j + 1) instanceof Character) {
                            char c2 = (Character) list.get(j + 1);
                            if (c2 >= '0' && c2 <= '9') {
                                flag = true;
                            }
                            if (c2 == '.') {
                                if (list.size() > j + 2 && list.get(j + 2) instanceof Character) {
                                    char c3 = (Character) list.get(j + 2);
                                    if (c3 >= '0' && c3 <= '9') {
                                        flag = true;
                                    }
                                }
                            }
                        }
                    }
                    if (c == '.') {
                        if (list.size() > j + 1 && list.get(j + 1) instanceof Character) {
                            char c2 = (Character) list.get(j + 1);
                            if (c2 >= '0' && c2 <= '9') {
                                flag = true;
                            }
                        }
                    }
                }
                if (flag) {
                    boolean isExp = false;
                    boolean isDec = false;
                    int oldI = j;
                    boolean neg = list.get(j).equals('-'), negExp = false;
                    if (neg) {
                        list.remove(j);
                    }
                    String num = neg ? "-" : "";
                    int exp = 0;
                    int dec = 0;
                    char c;
                    char c2;
                    for (; list.size() > j && list.get(j) instanceof Character;) {
                        c = (Character) list.get(j);
                        if (list.size() < j + 2) {
                            c2 = '\0';
                        } else if (!(list.get(j + 1) instanceof Character)) {
                            c2 = '\0';
                        } else {
                            c2 = (Character) list.get(j + 1);
                        }
                        if ((c < '0' || c > '9') && c != 'e' && (c != '.' || c2 < '0' || c2 > '9')) {
                            break;
                        }
                        if (c == '.') {
                            isDec = true;
                        } else if (c == 'e') {
                            isExp = true;
                            negExp = list.get(j + 1).equals('-');
                            if (negExp) {
                                list.remove(j + 1);
                            }
                        } else {
                            if (isExp) {
                                exp = 10 * exp + (c - '0');
                            } else {
                                num += c;
                                if (isDec) {
                                    dec++;
                                }
                            }
                        }
                        list.remove(j);
                    }
                    if (negExp) {
                        exp = -exp;
                    }
                    list.add(oldI, new LNumber(new BigDecimal(new BigInteger(num), dec - exp)));
                }
            }
        }
        ;
        adjustMain(lists.get(0));
        lists.stream()
                .map(this::withSubfunctionsAndIfs)
                .filter(list -> !list.isEmpty())
                .forEach(list -> {
                    char c;
                    if (list.get(2) instanceof Character && ((c = (Character) list.get(2)) >= 'a' && c <= 'z')) {
                        constants.put(list.get(1).toString(), new NamedFunction(list, constants));
                    } else {
                        constants.put(list.get(1).toString(), new NamedSupplier(list, constants));
                    }
                });
        return new Executable(constants, s);
    }

    private List adjustMain(List list) {
        list.add(0, '[');
        list.add(1, 'M');
        list.add(']');
        if (list.contains('x')) {
            list.add(2, 'x');
            list.add(']');
            if (list.contains('y')) {
                list.add(3, '[');
                list.add(4, 'y');
                list.add(']');
                if (list.contains('z')) {
                    list.add(5, '[');
                    list.add(6, 'z');
                    list.add(']');
                }
            }
        } else {
            list.add(2, ' ');
        }
        return list;
    }

    private List withSubfunctionsAndIfs(List list) {
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i).equals('[')) {
                int j = i + 1;
                // continue until next ']'
                for (int count = 1; j < list.size() && count != 0; j++) {
                    if (list.get(j).equals('[')) {
                        count++;
                    } else if (list.get(j).equals('?')) {
                        count += 2;
                    } else if (list.get(j).equals(']')) {
                        count--;
                    }
                }
                List l = new ArrayList();
                for (int k = i; k < j; k++) {
                    Object remove = list.remove(i);
                    l.add(remove);
                }
                l = withSubfunctionsAndIfs(l);
                boolean declaredVariable = false;
                if (l.get(1) instanceof Character) {
                    char c = (Character) l.get(1);
                    declaredVariable = c >= 'a' && c <= 'z';
                }
                if (!declaredVariable) {
                    l.add(1, 'x');
                    l.add(l.size() - 1, 'x');
                }
                list.add(i, new LocFunction(l, constants));
            }
        }
        determineIfs(list);
        return list;
    }

    private List determineIfs(List list) {
        list.stream()
                .filter(o -> o instanceof LocFunction)
                .forEach(o -> determineIfs(((LocFunction) o).list()));
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i).equals('?')) {
                int j = i + 1;
                // continue until next two ']'
                for (int count = 2; j < list.size() && count != 1; j++) {
                    if (list.get(j).equals('?')) {
                        count += 2;
                    } else if (list.get(j).equals(']')) {
                        count--;
                    }
                }
                int then = j - 1;
                for (int count = 1; j < list.size() && count != 0; j++) {
                    if (list.get(j).equals('?')) {
                        count += 2;
                    } else if (list.get(j).equals(']')) {
                        count--;
                    }
                }
                int els = j - 1;
                If lif = new If(then, els);
                list.set(i, lif);
                list.set(then, ' ');
                list.set(els, ' ');
            }
        }
        return list;
    }

    private List closeBrackets(List list) {
        long ifs = list.stream().filter(c -> c.equals('?')).count();
        long starts = list.stream().filter(c -> c.equals('[')).count();
        long ends = list.stream().filter(c -> c.equals(']')).count();
        for (int j = 0; j < 2 * ifs + starts - ends; j++) {
            list.add(']');
        }
        return list;
    }

    private String removeComments(String s) {
        return s.replaceAll("\\.-.*?-\\.", "");
    }

    private List expandBraces(List list) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals('}')) {
                list.set(i, ']');
                list.add(i + 1, '`');
            }
        }
        return list;
    }
}
